Passed
Push — master ( ce9bec...d1aed2 )
by
unknown
01:59
created

DateUtilsAdapter.getMonth   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
1
import { Injectable } from '@nestjs/common';
2
import {
3
  format as fnsFormat,
4
  isWeekend as fnsIsWeekend,
5
  getDaysInMonth as fnsGetDaysInMonth,
6
  eachDayOfInterval,
7
  addDays
8
} from 'date-fns';
9
import { MonthDate } from 'src/Application/Common/MonthDate';
10
import { IDateUtils } from 'src/Application/IDateUtils';
11
12
@Injectable()
13
export class DateUtilsAdapter implements IDateUtils {
14
  private makeDate(year: number, month: number, day: number): Date {
15
    /**
16
     * This creates a date in UTC timezone.
17
     *
18
     * The intent is to remedy possible timezone issues.
19
     *
20
     * Indeed, you might be tempted to write code like this:
21
     *
22
     * ```js
23
     * const d = new Date(year, month, day);
24
     * ```
25
     *
26
     * Sadly, this is wrong.
27
     *
28
     * Indeed, if your computer's (or the server's) timezone is not UTC, then
29
     * the actual `day` stored in `d` might be different.
30
     *
31
     * For example, if your computer is on UTC+1 (Paris time), then..
32
     *
33
     * ```js
34
     * > new Date(2022, 11, 14).toISOString() // Dec 14th, 2022
35
     * 2022-12-13T23:00:00.000Z // Oops, it was stored as Dec 13th!
36
     * ```
37
     *
38
     * (Yes, timezones are a bit of a pain.)
39
     *
40
     * To remedy this, we create a date from an ISO date string (yyyy-mm-dd),
41
     * with leading zero padding and all that.
42
     *
43
     * JavaScript's `Date()` will properly interpret this as an UTC-timezoned date.
44
     */
45
    const fMonth = String(month).padStart(2, '0');
46
    const fDay = String(day).padStart(2, '0');
47
    return new Date(`${year}-${fMonth}-${fDay}`);
48
  }
49
50
  public format(date: Date, format: string): string {
51
    return fnsFormat(date, format);
52
  }
53
54
  public getDaysInMonth(date: Date): number {
55
    return fnsGetDaysInMonth(date);
56
  }
57
58
  public isWeekend(date: Date): boolean {
59
    return fnsIsWeekend(date);
60
  }
61
62
  public isAWorkingDay(date: Date): boolean {
63
    if (this.isWeekend(date)) {
64
      return false;
65
    }
66
67
    const workedFreeDays = this.getWorkedFreeDays(this.getYear(date));
68
    const formatedDate = this.format(date, 'yyyy-MM-dd');
69
70
    for (const day of workedFreeDays) {
71
      const formatedDay = this.format(day, 'yyyy-MM-dd');
72
73
      if (formatedDate === formatedDay) {
74
        return false;
75
      }
76
    }
77
78
    return true;
79
  }
80
81
  public getCurrentDate(): Date {
82
    return new Date();
83
  }
84
85
  public getYear(date: Date): number {
86
    return date.getUTCFullYear();
87
  }
88
89
  public getMonth(date: Date): MonthDate {
90
    return new MonthDate(date.getUTCFullYear(), date.getUTCMonth() + 1);
91
  }
92
93
  public getLastDayOfYear(date: Date): Date {
94
    return this.makeDate(this.getYear(date), 12, 31);
95
  }
96
97
  public getFirstDayOfYear(date: Date): Date {
98
    return this.makeDate(this.getYear(date), 1, 1);
99
  }
100
101
  public getCurrentDateToISOString(): string {
102
    return this.getCurrentDate().toISOString();
103
  }
104
105
  public addDaysToDate(date: Date, days: number): Date {
106
    return addDays(date, days);
107
  }
108
109
  public getWorkedDaysDuringAPeriod(start: Date, end: Date): Date[] {
110
    const dates: Date[] = [];
111
    const workedFreeDays: Date[] = [];
112
113
    for (let year = this.getYear(start); year <= this.getYear(end); year++) {
114
      workedFreeDays.push(...this.getWorkedFreeDays(year));
115
    }
116
117
    for (let day of eachDayOfInterval({ start, end })) {
118
      // date-fns returns local-timezone dates. Be sure to convert to
119
      // UTC-timezoned dates for ISO string comparison with work-free days.
120
      day = new Date(this.format(day, 'yyyy-MM-dd'));
121
122
      if (
123
        this.isWeekend(day) ||
124
        workedFreeDays.filter(d => d.toISOString() === day.toISOString())
125
          .length > 0
126
      ) {
127
        continue;
128
      }
129
130
      dates.push(day);
131
    }
132
133
    return dates;
134
  }
135
136
  public getWorkedFreeDays(year: number): Date[] {
137
    const fixedDays: Date[] = [
138
      this.makeDate(year, 1, 1), // New Year's Day
139
      this.makeDate(year, 5, 1), // Labour Day
140
      this.makeDate(year, 5, 8), // Victory in 1945
141
      this.makeDate(year, 7, 14), // National Day
142
      this.makeDate(year, 8, 15), // Assumption
143
      this.makeDate(year, 11, 1), // All Saints' Day
144
      this.makeDate(year, 11, 11), // The Armistice
145
      this.makeDate(year, 12, 25) // Christmas
146
    ];
147
148
    const easterDate = this.getEasterDate(year);
149
    const easterDays: Date[] = [
150
      addDays(easterDate, 1), // Easter Monday
151
      addDays(easterDate, 39) // Ascension
152
    ];
153
154
    return [...fixedDays, ...easterDays];
155
  }
156
157
  public getEasterDate(year: number): Date {
158
    const a = year % 19;
159
    const b = Math.floor(year / 100);
160
    const c = year % 100;
161
    const d = Math.floor(b / 4);
162
    const e = b % 4;
163
    const f = Math.floor((b + 8) / 25);
164
    const g = Math.floor((b - f + 1) / 3);
165
    const h = (19 * a + b - d - g + 15) % 30;
166
    const i = Math.floor(c / 4);
167
    const k = c % 4;
168
    const l = (32 + 2 * e + 2 * i - h - k) % 7;
169
    const m = Math.floor((a + 11 * h + 22 * l) / 451);
170
    const n0 = h + l + 7 * m + 114;
171
    const n = Math.floor(n0 / 31);
172
    const p = (n0 % 31) + 1;
173
174
    return this.makeDate(year, n, p);
175
  }
176
177
  public getLeaveDuration(
178
    startDate: string,
179
    isStartsAllDay: boolean,
180
    endDate: string,
181
    isEndsAllDay: boolean
182
  ): number {
183
    let duration = this.getWorkedDaysDuringAPeriod(
184
      new Date(startDate),
185
      new Date(endDate)
186
    ).length;
187
188
    if (false === isStartsAllDay) {
189
      duration -= 0.5;
190
    }
191
192
    if (false === isEndsAllDay && duration > 0.5) {
193
      duration -= 0.5;
194
    }
195
196
    return duration;
197
  }
198
}
199